package gbench.sandbox.matrix.img.util;

import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.Tuple2.TUP2;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.imageio.ImageIO;

import gbench.common.tree.LittleTree.IRecord;
import gbench.common.tree.LittleTree.Tuple2;

/**
 * 图像常用操作
 * @author gbench
 *
 */
public class ImageOps {
    /**
     * 图片数据读取
     * @param imgfile 图片路径
     * @return rgbinfo 数据摘要 ,{width,height, rgbs:[x][y][r,g,b]}
     */
    public static IRecord img2rgbinfo(final String imgfile) {
        final var input = new File(imgfile);
        BufferedImage bimg = null;
        try {
            bimg = ImageIO.read(input);// 读取数据
        } catch (IOException e) {
            e.printStackTrace();
        }//读取本地图片
        
        return img2rgbinfo(bimg);
    }
    
    /**
     * 图片数据读取
     * @param imgfile 图片路径
     * @return rgbinfo 数据摘要 ,{width,height, rgbs:[x][y][r,g,b]}
     */
    public static IRecord img2rgbinfo(final BufferedImage bimg) {
        final var rgbinfo = REC("height",0,"width",0,"rgbs",new int[][][] {}); // 默认结果状态
        if(bimg==null)return rgbinfo;
        
        final var height = bimg.getHeight();
        final var width =bimg.getWidth();
        final var ras = bimg.getData();// 读取栅格数据
        final var rgbs = new int[width][height][3];// rgb 数据 数组[r,g,b]
        
        rgbinfo.add(REC("width",width,"height",height,"ras",ras,"rgbs",rgbs)); // 
        for(int y=0;y<height;y++) {// y 高度
            for(int x=0;x<width;x++) { // x 水平
                ras.getPixel(x, y, rgbs[x][y]);// 读取像素数据
            }// x
        }// for y
        
        return rgbinfo;
    }

    /**
     * 颜色向量空间
     * @param imgfile 图片路径
     * @return 颜色向量空间
     */
    public static List<int[]> img2xyrgbs(final String imgfile){
        final var rgbinfo = img2rgbinfo(imgfile);
        return rgbinfo2xyrgbs(rgbinfo);
    }

    /**
     * 颜色向量空间
     * @param bimg 图片路径
     * @return 颜色向量空间
     */
    public static List<int[]> img2xyrgbs(final BufferedImage bimg){
        final var rgbinfo = img2rgbinfo(bimg);
        return rgbinfo2xyrgbs(rgbinfo);
    }
    
    /**
     * 生成颜色向量空间
     * @param rec 颜色数据 {width,height, rgbs:[[r,g,b]]}
     * @return 颜色向量空间 [[width,height, r,g,b]]
     */
    public static List<int[]> rgbinfo2xyrgbs(final IRecord rec){
        final var xyrgbs = new LinkedList<int[]>();// 颜色空间
        
        try {
            final var height = rec.i4("height");
            final var width = rec.i4("width");
            final var rgbs = (int[][][])rec.get("rgbs");
            for(int y=0;y<height;y++) {// y
                for(int x=0;x<width;x++) {// x
                    xyrgbs.add(new int[] {x,y,rgbs[x][y][0],rgbs[x][y][1],rgbs[x][y][2]});
                }// x
            }// for y
        }catch(Exception e) {
            e.printStackTrace();
        }
        
        return xyrgbs;
    }

    /**
     * rgb 数据颜色转 图片数据。
     * @param width 图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final int width,final int height, final List<int[]> xyrgbs) {
       final var bufferedImage = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB);
       return render(bufferedImage,xyrgbs.stream(),2);
    }
    
    /**
     * rgb 数据颜色转 图片数据。
     * @param width 图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final int width,final int height, final Stream<int[]> xyrgbs) {
       final var bufferedImage = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB);
       return render(bufferedImage,xyrgbs,2);
    }
    
    /**
     * rgb 数据颜色转 图片数据。
     * @param width 图片宽度
     * @param height 图片高度
     * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
     * @return BufferedImage 图像数据。
     */
    public static BufferedImage xyrgbs2bimg(final Number width, final Number height, final List<Integer[]> xyrgbs) {
        final var bufferedImage = new BufferedImage(width.intValue(),height.intValue(), BufferedImage.TYPE_INT_RGB);
        return render(bufferedImage,unbox(xyrgbs).stream(),2);
    }
    
    /**
    * rgb 数据颜色转 图片数据。
    * @param width 图片宽度
    * @param height 图片高度
    * @param xyrgbs 色彩空间 (x：水平坐标从0开始,y:列坐标从0开始,color,颜色数据),
    * @return BufferedImage 图像数据。
    */
   public static BufferedImage xyrgbs2bimg(final Number width, final Number height, final Stream<Integer[]> xyrgbs) {
       final var bufferedImage = new BufferedImage(width.intValue(),height.intValue(), BufferedImage.TYPE_INT_RGB);
       return render(bufferedImage,unbox(xyrgbs).stream(),2);
   }
    
    /**
     * rgb 数据颜色转 图片数据。
     * @param rgbs 三维色彩空间矩阵  [x][y][r,g,b]
     * @return BufferedImage
     */
    public static BufferedImage rgbs2bimg(final int[][][] rgbs) {
        final int width = rgbs.length;;
        final int height = rgbs[0].length;
        final var bimg = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB);
        final var ras = bimg.getRaster();
        for(int y=0;y<height;y++) {// y
            for(int x=0;x<width;x++) { // x
                ras.setPixel(x, y, rgbs[x][y]);
            }// for x
        }// for y
        return bimg;
    }

    /**
     * 使用xyrgbs 描绘 bufferedImage
     * @param bufferedImage 内存图片
     * @param xyrgbs 色彩向量组
     * @param start 色彩开始位置偏移
     * @return BufferedImage
     */
    public static BufferedImage render(final BufferedImage bufferedImage,final List<int[]> xyrgbs,final int start) {
        return render(bufferedImage,xyrgbs,start);
    }
    
    /**
     * 使用xyrgbs 描绘 bufferedImage
     * @param bufferedImage
     * @param vectors 色彩向量组 [x,y,...,r,g,b,...]
     * @param start 色彩开始位置偏移
     * @return BufferedImage
     */
    public static BufferedImage render(final BufferedImage bufferedImage,final Stream<int[]> vectors,final int start) {
        final var ras = bufferedImage.getRaster();// 获取网状栅格
        split_handle(tup->{ // 向量分解处理
            final var index = tup._1(); // 向量坐标索引信息
            final var rgb = tup._2(); // 向量值信息
            try {// 设置栅格像素
                ras.setPixel( index[0], index[1], rgb ); // 设置色彩值
            }catch(Exception e) {// 数据溢出
                System.err.println("索引位置("+box2list(index)+") 设置 ("+box2list(rgb)+") 失败！");
            }// try
        },vectors,start);
        
        return bufferedImage;
    }
    
    /**
     * 向量的分解&处理
     * @param handler (index,value)->{} // 把向量分解成  index和数值的二元组 进行处理。
     * @param vectors 值向量的处理
     * @param start 色彩开始位置偏移
     */
    public static void split_handle(final Consumer<Tuple2<int[],int[]>>handler,final Stream<int[]> vectors,final int start) {
        // 向量分解&处理
        vectors.forEach(v -> { // [x,y,...,r,g,b,...]
            final var index = Arrays.copyOfRange( v, 0, start );// 提取坐标 数据
            final var value = Arrays.copyOfRange( v, start, v.length );// 提取数值数据
            handler.accept(TUP2(index,value));
        });// forEach
    }
    
    /**
     * 数据装箱
     * int[] -> Integer[]
     * @param aa 基础类型的数组
     * @return 整形数组转整形对象数组。
     */
    public static List<Integer[]> box(List<int[]> aa) {
        return aa.stream().map(e->Arrays.stream(e).boxed().toArray(Integer[]::new)).collect(Collectors.toList());
    }
    
    /**
     * 数据装箱
     * Integer[] -> int[] 
     * @param aa 基础类型的数组
     * @return 整形数组转整形对象数组。
     */
    public static List<int[]> unbox(List<Integer[]> aa) {
        return aa.stream().map(e->Arrays.stream(e).mapToInt(p->p).toArray()).collect(Collectors.toList());
    }
    
    /**
     * 数据装箱
     * Integer[] -> int[] 
     * @param stream 基础类型的数组
     * @return 整形数组转整形对象数组。
     */
    public static List<int[]> unbox(Stream<Integer[]> stream) {
        return stream.map(e->Arrays.stream(e).mapToInt(p->p).toArray()).collect(Collectors.toList());
    }
    
    /**
     * 把一个int[]转换成一个List
     * @param aa int[]类型的对象
     * @return 整形元素构成的队列
     */
    public static List<Integer> box2list(int[] aa){
        return Arrays.stream(aa).mapToObj(Integer::valueOf).collect(Collectors.toList());
    }

    /**
     * 判断数值是否位于start和end之间 <br>
     * x>=start&&x<end
     * @param start 起始值 inclusive
     * @param end 终止值 exclusive
     * @return 区间判断函数
     */
    public static Predicate<Number> between(final Number start,final Number end){
        return x->x.doubleValue()>=start.doubleValue() && x.doubleValue()<end.doubleValue();
    }

    /**
     * 写出一张图片。
     * @param rgb 颜色数组
     * @param path 图片数据路径
     */
    public static BufferedImage jpgwrite(final int[][][] rgbs,final String path) {
        return imgwrite(rgbs2bimg(rgbs),path,DEFAULT_FORMAT_NAME);   
    }
    
    /**
     * 写出一张图片。
     * @param bimg 颜色数组
     * @param path 图片数据路径
     * @param formatName 图片格式,null 默认为jpg
     */
    public static BufferedImage jpgwrite(final BufferedImage bufferedImage,final String path) {
        return imgwrite(bufferedImage,path,DEFAULT_FORMAT_NAME);
    }
    
    /**
     * 写出一张图片。
     * @param bimg 颜色数组
     * @param imgfile 图片数据路径
     * @param formatName 图片格式,null 默认为jpg
     * @param bgcolor 背景颜色
     */
    public static BufferedImage jpgwrite(final BufferedImage bufferedImage,final String imgfile,final Color bgcolor) {
        return imgwrite(bufferedImage,imgfile,DEFAULT_FORMAT_NAME,bgcolor);
    }
    
    /**
     * 写出一张图片。
     * @param bimg 颜色数组
     * @param imgfile 图片数据路径
     * @param formatName 图片格式,null 默认为jpg
     */
    public static BufferedImage imgwrite(final BufferedImage bufferedImage,final String imgfile,final String formatName) {
        return imgwrite(bufferedImage, imgfile, formatName, (Color)null);
    }
    
    /**
     * 写出一张图片。
     * @param bimg 颜色数组
     * @param imgfile 图片数据路径
     * @param formatName 图片格式,null 默认为jpg
     * @param bgcolor 背景颜色
     */
    public static BufferedImage imgwrite(final BufferedImage bufferedImage,final String imgfile,final String formatName,final int[]bgcolor) {
        return imgwrite(bufferedImage, imgfile, formatName, rgb2clr(bgcolor));
    }
    
    /**
     * 写出一张图片。
     * @param bimg 颜色数组
     * @param imgfile 图片数据路径
     * @param formatName 图片格式,null 默认为jpg
     * @param bgcolor 背景颜色
     */
    public static BufferedImage imgwrite(final BufferedImage bufferedImage,final String imgfile,final String formatName,final Color bgcolor) {
        try {
            final var g = bufferedImage.createGraphics();// 绘图图片对象
            //g.setBackground(bgcolor);
            g.drawImage(bufferedImage, 0, 0, bgcolor==null?Color.WHITE:bgcolor, null); //背景填充色设置为白色
            final var file = new File(imgfile);
            ImageIO.write(bufferedImage, formatName==null?DEFAULT_FORMAT_NAME:formatName, file);
        } catch (Exception e) {
            e.printStackTrace();
        }// try
        return bufferedImage;
    }
    
    /**
     * rgb数组转颜色
     * @param rgb rgb数组
     * @return 颜色对象
     */
    public static Color rgb2clr(final int[] rgb) {
        if(rgb==null)return Color.black;
        return new Color(rgb[0],rgb[1],rgb[2]);
    }
    
    /**
     * 随机的颜色
     * @return 随机rgb颜色数组
     */
    public static int[] rndrgb() {
        return new int[] {rand.nextInt(256),rand.nextInt(256),rand.nextInt(256)};
    }
    
    /**
     * 颜色转rgb
     * @param color颜色对象
     * @return rgb数组
     */
    public static int[] clr2rgb(final Color clr) {
        return new int[] {clr.getRed(),clr.getGreen(),clr.getBlue()};
    }
    
    /**
     * 求一个数据的平方数
     * @param x 数值
     * @return 平方数
     */
    public static Double square(final Number x) {
        return x.doubleValue()*x.doubleValue();
    }
    
    /**
     * 求一个数据的立方数
     * @param x 数值
     * @return 立方数
     */
    public static Double cube(final Number x) {
        return x.doubleValue()*x.doubleValue()*x.doubleValue();
    }
    
    /**
     * 随机生成一个 [0,bound)之间的整数
     * @param bound 数量上边界
     * @return 随机整数
     */
    public static Integer rndint(final int bound) {
        return rand.nextInt(bound);
    }
    
    /**
     * 随机生成一个 [0,1)之间的 浮点数
     * @return 随机浮点数
     */
    public static Double rnddbl() {
        return rand.nextDouble();
    }
    
    public static String DEFAULT_FORMAT_NAME = "jpg"; // 默认的图片格式
    public final static Random rand = new Random();

}
